//
//  controller.cpp
//  ampcontroller
//
//  Created by Developer on 20/01/2014.
//  Copyright (c) 2014 Maestun Abidjan. All rights reserved.
//

#include "controller.h"

//typedef enum {
//    EPinButtonVoid = -1,
//    EPinButtonSelect = D2,
//    EPinButtonPrev,
//    EPinButtonNext,
//    EPinButtonLast
//} EPinButton;


#define FOOTSW_LONGPRESS_DELAY      (2000)
#define EEPROM_LAST_PATCH_ADDRESS   (0x0)
#define EEPROM_PATCH_ADDRESS        (0x1)
#define LCD_COLUMNS                 (16)
#define LCD_ROWS                    (2)

#define PIN_BUTTON_SELECT           (D2)
#define PIN_BUTTON_NEXT             (D3)
#define PIN_BUTTON_PREV             (D4)

#define PIN_LCD_RS                  (A0)
#define PIN_LCD_ENABLE              (A1)
#define PIN_LCD_D4                  (A2)
#define PIN_LCD_D5                  (A3)
#define PIN_LCD_D6                  (A4)
#define PIN_LCD_D7                  (A5)

#define PIN_EXPR                    (A6)

#define PIN_BLUETOOTH_TX            (D11)
#define PIN_BLUETOOTH_RX            (D12)


// ====================================================================================
#pragma mark - GLOBALS
// ====================================================================================
EState          gState;
uint32_t        gTimestamp;
bool            gLongPress;
bool            gLockFootsw;
uint8_t         gPrevFootsw;
uint16_t        gPedalStep;
uint8_t         gCurrentPatchNum;
uint8_t         gNextPatchNum;
SPatchData      gPatches[MAX_PATCHES];
SoftwareSerial  gBluetoothSerial(PIN_BLUETOOTH_RX, PIN_BLUETOOTH_TX);
LiquidCrystal   gLCD(PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7);
uint8_t         gLCDColumn; // in edit mode, index of current LCD character
uint8_t         gLCDChar;   // in edit mode, current character



// ====================================================================================
#pragma mark - EEPROM ROUTINES
// ====================================================================================
// loads all effect patch values into gPatches
void EEPROM_LoadAllValues() {
    
    gNextPatchNum = EEPROM.read(EEPROM_LAST_PATCH_ADDRESS);
    for(uint8_t patch = 0; patch < MAX_PATCHES; patch++) {

        uint8_t name_offset = EEPROM_PATCH_ADDRESS + (patch * MAX_INPUT_POTS);
        uint8_t step_offset = EEPROM_PATCH_ADDRESS + MAX_NAME_LEN + (patch * MAX_INPUT_POTS);
        
        // load name
        for(uint8_t c = 0; c < MAX_NAME_LEN; c++) {
            gPatches[patch].name[c] = EEPROM.read(name_offset + c);
        }
        
        // load step values
        for(uint8_t pot = 0; pot < MAX_INPUT_POTS; pot++) {
            gPatches[patch].step[pot] = EEPROM.read(step_offset + pot);
            Serial.print("loaded patch ");
            Serial.print(patch, DEC);
            Serial.println(":");
            Serial.print(gPots[pot].name);
            Serial.print(" : ");
            Serial.println(gPatches[patch].step[pot], DEC);
        }
    }
}


// updates EEPROM (and memory if preloading all) with patch changes
void EEPROM_SavePatch() {
    Serial.print("saving patch n°");
    Serial.print(gCurrentPatchNum, DEC);
    Serial.println("...");
    
    uint8_t name_offset = EEPROM_PATCH_ADDRESS + (gCurrentPatchNum * MAX_INPUT_POTS);
    uint8_t step_offset = EEPROM_PATCH_ADDRESS + MAX_NAME_LEN + (gCurrentPatchNum * MAX_INPUT_POTS);

    // save name
    for(uint8_t c = 0; c < MAX_NAME_LEN; c++) {
        EEPROM.write(name_offset + c, gPatches[gCurrentPatchNum].name[c]);
    }

    // save step values
    for(uint8_t pot = 0; pot < MAX_INPUT_POTS; pot++) {
        EEPROM.write(step_offset + pot, gPatches[gCurrentPatchNum].step[pot]);
        Serial.print(gPots[pot].name);
        Serial.print(" : ");
        Serial.println(gPatches[gCurrentPatchNum].step[pot], DEC);

    }
    Serial.println("patch saved.");
}


// updates EEPROM with last selected patch
void EEPROM_SaveLast() {
    EEPROM.write(EEPROM_LAST_PATCH_ADDRESS, gCurrentPatchNum);
}


// ====================================================================================
#pragma mark - LCD ROUTINES
// ====================================================================================

/*
 idle, patch selected, expr pedal up
 PATCH_NAME__.P00
 V15T15M50B50G70.
 
 idle, next patch in preparation, expr pedal down
 PATCH_NAME__.P99
 NEXT_P_NAME_....
 
 idle, next patch sent to amp, exp pedal half
 NEXT_P_NAME_.P47
 V99T00M05B99G10.

 edit, cursor under current selected patch name
 NEXT_P_NAME_.P47
 V99T00M05B99G10.
*/

void LCD_UpdatePedal() {
    gLCD.setCursor(MAX_NAME_LEN - 3, 0);
    gLCD.print("P");
    gLCD.setCursor(MAX_NAME_LEN - 2, 0);
    gLCD.print((gPedalStep * 100) / MAX_INPUT_STEPS);
}


void LCD_Update() {
    
    // TODO: optimize name writing (not every time)
    
    // line 0 : patch name + expr pedal value
    gLCD.setCursor(0, 0);
    gLCD.print(gPatches[gCurrentPatchNum].name);
//    gLCD.setCursor(MAX_NAME_LEN - 3, 0);
//    gLCD.print("P");
//    gLCD.setCursor(MAX_NAME_LEN - 2, 0);
//    gLCD.print((gExprStep * 100) / MAX_INPUT_STEPS);
    
//    LCD_UpdatePedal();
    
    if((gState == EStateIdle) && (gNextPatchNum != gCurrentPatchNum)) {
        // line 1 : next patch name
        gLCD.setCursor(0, 1);
        gLCD.print(gPatches[gNextPatchNum].name);
    }
    else {
        // line 1 : show values (format is v74 => Volume 74%)
        gLCDColumn = 0;
        for(uint8_t pot = 0; pot < MAX_INPUT_POTS; pot++) {
            // pot name first letter TODO: negative mode
            gLCD.setCursor(gLCDColumn, 1);
            gLCD.print(gPots[pot].name[0]);
            gLCDColumn++;
            
            // pot value in %
            uint8_t step = (gPatches[gCurrentPatchNum].step[pot] * 100) / MAX_OUTPUT_STEPS;
            if(step < 10) {
                gLCD.print(0);
                gLCD.print(step);
            }
            else {
                if(step > 99) {
                    step == 99;
                }
                gLCD.print(step);
                gLCDColumn++;
                gLCDColumn++;
            }
        }
    }
}




// ====================================================================================
#pragma mark - BLUETOOTH ROUTINES
// ====================================================================================

// Send BT commands (in Idle state only): when changing a patch on the controller, it will
// a command made of state, then MAX_INPUT_POTS pair of bytes : pot index, then pot step value.
void BT_Send() {
    if((gState == EStateIdle)) {
        // idle mode : send new patch to amp only if it changed
        if(gNextPatchNum != gCurrentPatchNum) {
            while(gBluetoothSerial.available() == false)
                ;
                
            gBluetoothSerial.write(gState);
            for(uint8_t pot = 0; pot < MAX_INPUT_POTS; pot++) {
                gBluetoothSerial.write(pot);
                gBluetoothSerial.write(gPatches[gNextPatchNum].step[pot]);
                Serial.print("BT SEND : ");
                Serial.print(gPots[pot].name);
                Serial.print(" : ");
                Serial.println(gPatches[gNextPatchNum].step[pot], DEC);
            }
            gCurrentPatchNum = gNextPatchNum;
        }
    }
}


// Read BT commands : when changing a pot value on the amp, it will send
// a command made of 2 bytes : pot index, then pot step value.
// This does not depend on the state, amp always has the priority.
void BT_Read() {
    if(gBluetoothSerial.available()) {
        int pot = gBluetoothSerial.read();
        int step = gBluetoothSerial.read();
        
        if(pot > -1 && step > -1) {
            Serial.print("BT RECV : ");
            Serial.print(gPots[pot].name);
            Serial.print(" : ");
            Serial.println(step, DEC);
            gPatches[gCurrentPatchNum].step[pot] = step;
        }
    }
}


// ====================================================================================
#pragma mark - HARDWARE BUTTON SCAN
// ====================================================================================

// callback for longpress event
void HW_OnFootswitchLongPress(uint8_t aPinNumber) {
Serial.print("FOOTSWITCH LONGPRESS ");
Serial.println(aPinNumber, DEC);
    if(aPinNumber == PIN_BUTTON_SELECT) {
        if(gState == EStateEdit) {
            // save and quit edit mode
            gState = EStateIdle;
            EEPROM_SavePatch();
        }
        else if(gState == EStateIdle) {
            // enter edit mode
            gState = EStateEdit;
        }
    }
}


// callback for click event
void HW_OnFootswitchClick(uint8_t aPinNumber) {
Serial.print("FOOTSWITCH CLICK ");
Serial.println(aPinNumber, DEC);

    switch (aPinNumber) {
        case PIN_BUTTON_SELECT: {
            if(gState == EStateEdit) {
            }
            else if(gState == EStateIdle) {
                // select next patch
                
            }
        } break;
        case PIN_BUTTON_PREV: {
            if(gState == EStateEdit) {
            }
            else {
                // prev patch
            }

        } break;
        case PIN_BUTTON_NEXT: {
            if(gState == EStateEdit) {
            }
            else {
                // next patch

            }
        } break;
        default:
            break;
    }
}


// called when a footsw is not pressed
void HW_OnPinLow(uint8_t aPinNumber) {
    gLockFootsw = false;
Serial.print("HW_OnPinLow : ");
Serial.println(aPinNumber, DEC);
	if(aPinNumber == gPrevFootsw) {
		// unclick
		if(gLongPress == false) {
            HW_OnFootswitchClick(aPinNumber);
		}
		else {
			// unlongpress
			gLongPress = false;
			HW_OnFootswitchLongPress(aPinNumber);
		}
		gPrevFootsw = -1;
	}
	else {
		// normal state, do nothing
	}
}


// called when a footsw is pressed
void HW_OnPinHigh(uint8_t aPinNumber) {
    
    // this will prevent to go here on each loop
    gLockFootsw = true;
    
Serial.print("HW_OnPinHigh : ");
Serial.println(aPinNumber, DEC);
    
    // previous code w/ longpress detection
	if(aPinNumber == gPrevFootsw) {
		// same pin still pressed
		if(gLongPress == false && (millis() - gTimestamp) >= FOOTSW_LONGPRESS_DELAY) {
			gLongPress = true;
			HW_OnFootswitchLongPress(aPinNumber);
		}
	}
	else {
		// other pin pressed
		gTimestamp = millis();
		gPrevFootsw = aPinNumber;
	}

//    
//    
//    // =================================================================================
//    // BANK UP
//    // =================================================================================
//    if(aPinNumber == EPinButtonNext) {
//        if(gState == EStateEdit) {
//            // next pot
//        }
//        else {
//            // change patch (and maybe loop to start)
//        }
//    }
//    // =================================================================================
//    // BANK DOWN
//    // =================================================================================
//    else if(aPinNumber == EPinButtonPrev) {
//        if(gState == EStateEdit) {
//            // prev pot
//        }
//        else {
//            // change bank (and maybe loop to end)
//        }
//    }
//    // =================================================================================
//    // STORE : quit programming mode and save
//    // =================================================================================
//    else if(aPinNumber == EPinButtonSelect) {
//        if(gState == EStateEdit) {
//            // we were programming: save and leave programming mode
//            // EEPROM_SavePatch();
//            // gState = EStateIdle;
//        }
//        else if(gState == EStateIdle) {
//            // apply patch
//        }
//    }
//
//    
//    // =================================================================================
//    // LONGPRESS DETECTION
//    // =================================================================================
//	if(aPinNumber == gPrevFootsw) {
//		// same pin still pressed
//		if(gLongPress == false && (millis() - gTimestamp) >= FOOTSW_LONGPRESS_DELAY) {
//			gLongPress = true;
//			HW_OnFootswitchLongpress(aPinNumber);
//		}
//	}
//	else {
//		// other pin pressed
//		gTimestamp = millis();
//		gPrevFootsw = aPinNumber;
//	}
}


// call this every loop to scan all the footswitches
void HW_ScanFootswitch() {
    delay(DEBOUNCE_FILTER_DELAY);
    for(uint8_t footsw = PIN_BUTTON_SELECT; footsw <= PIN_BUTTON_PREV; footsw++) {
        if(digitalRead(footsw) == HIGH) {
            if(gLockFootsw == false) {
                HW_OnPinHigh(footsw);
            }
        }
        else {
            HW_OnPinLow(footsw);
        }
    }
}


void HW_ScanPedal() {
    uint16_t step = analogRead(PIN_EXPR);
    if(step != gPedalStep) {
        gPedalStep = step;
        LCD_UpdatePedal();
    }
}


// ====================================================================================
#pragma mark - MAIN ROUTINES
// ====================================================================================
void setup_controller() {
    
    Serial.begin(9600);
    
    gState = EStateIdle;
    gLockFootsw = false;
    gPrevFootsw = -1;
    gPedalStep = -1;
    
    // configure pins
    pinMode(PIN_BUTTON_SELECT, INPUT);
    pinMode(PIN_BUTTON_NEXT, INPUT);
    pinMode(PIN_BUTTON_PREV, INPUT);
   
    
    // set current patch to bullshite value to force patch loading
    gCurrentPatchNum = -1;
    
    gLCD.begin(LCD_COLUMNS, LCD_ROWS);
    gLCD.setCursor(0, 0);
    gLCD.print("AMP CTRL v");
    gLCD.print(VERSION_MAJOR);
    gLCD.print(".");
    gLCD.print(VERSION_MINOR);
    
    gLCD.setCursor(0, 1);
    gLCD.print("Loading...");
    EEPROM_LoadAllValues();
    gLCD.setCursor(0, 1);
    gLCD.print("Ready !");
}

void loop_controller() {
 
    HW_ScanFootswitch();
    
    HW_ScanPedal();
    
    LCD_Update();
    
    BT_Send();
    
    BT_Read();
    
}


// EOF